Web3 的攻擊面不只來自人與裝置,也深藏在合約的邏輯與呼叫順序裡。今天用一張「開發者視角」的地圖,把最常見且反覆出現的弱點一次釐清,強化後續審閱與風控的直覺。
當合約對外呼叫(轉 ETH、呼叫其他合約)時,對方可在狀態更新前回呼本合約,重複提領或破壞不變量。經典對策是 Checks–Effects–Interactions(CEI) 先檢查、再改狀態、最後才對外互動;或以 ReentrancyGuard 等機制護欄化敏感函式;必要時採 Pull Payment(由收款方主動提領)。
approve(spender, amount) 的改值競態可能被 front-run:當你把 N 改成 M,對手可先花掉 N、再拿到 M;而把 amount 設為 type(uint256).max 的無限授權,一旦 DApp/合約遭入侵,攻擊者可長期代扣。緩解包含:優先使用 increaseAllowance/decreaseAllowance、限額/限時授權、定期撤銷不用的授權。
tx.origin 身分驗證誤用以 tx.origin 做權限判斷會被釣魚合約繞過:攻擊者引導使用者對惡意合約交易,使 tx.origin 仍是使用者,但 msg.sender 為惡意合約,導致誤放行。授權請用 msg.sender;tx.origin 僅作「是否為 EOA」等輔助判斷。
delegatecall 與升級代理:儲存衝突與任意執行delegatecall 以呼叫者的儲存上下文執行目標位址程式碼;若允許任意目標或儲存對齊不嚴謹,可能導致資金被提走或權限被覆蓋。升級代理(Transparent/UUPS)亦須嚴格限制升級入口、初始化流程與模組權限。
block.timestamp 可被調整礦工/驗證者對區塊時間戳有有限度的操控空間;以時間戳作抽獎、解鎖條件或計價,可能被偏移利用。可行方向:允許誤差、避免將 timestamp 作隨機源、必要時使用經過設計的隨機性(例如 VRF 類方案)。
unchecked/Yul 仍有風險自 0.8 起,算術預設溢位拋錯;但在 unchecked { ... } 區塊或 Yul/inline assembly 中仍可能繞過保護。審閱時需特別留意 unchecked、自定義位寬與組語段。
onlyOwner 之外還需思考操作分權、延時(Timelock)、緊急停機 Pausable 等。emit 並非狀態,記帳以狀態為準,事件僅輔助追蹤。合約安全的本質是流程支配權與狀態不變量的維持。外部互動要晚於狀態更新(CEI)、授權要極簡且可回收、身份判斷用 msg.sender、跨合約執行嚴控 delegatecall 與升級入口、時間戳別當隨機來源、審慎使用 unchecked 與組語。把這些原則做成「寫前自問、審前自查」的習慣,能在沒有專職審計的情況下,把大宗事故擋在門外。